import CryptoJS from 'crypto-js'
import { ChaCha20Poly1305 } from '@stablelib/chacha20poly1305'
import { streamXOR } from '@stablelib/salsa20'
import IDEA from 'idea-cipher'
// import {TWOFISH} from 'encryption-for-node';
// import { twofish } from 'twofish'
// import { SM4Util } from 'sm4util'
// import { SM4 } from 'gmsm-sm2js'
// // import {Twofish} from 'gnablib'


function intArrayToBase64(intArray) {
  const bytes = new Uint8Array(intArray)
  let binaryString = ''
  for (let i = 0; i < bytes.byteLength; i++) {
    binaryString += String.fromCharCode(bytes[i])
  }
  return btoa(binaryString)
}

function base64ToUint8Array(base64) {
  var binaryString = atob(base64)
  var bytes = new Uint8Array(binaryString.length)
  for (var i = 0; i < binaryString.length; i++) {
    bytes[i] = binaryString.charCodeAt(i)
  }
  return bytes
}

const rc4_encode = (keyObj, message) => {
  // console.log('rc4_encode', keyObj, message, keyObj.key);
  // console.log('rc4_encode1', CryptoJS.RC4.encrypt(message, '123'));
  // console.log('rc4_encode', CryptoJS.RC4.encrypt(message, keyObj.key));
  if (keyObj.code != CryptoCode.RC4) {
    console.log('keyObj.code', keyObj.code, CryptoCode.RC4);

    return 'Crypto mismatch!'
  }
  var key = CryptoJS.enc.Utf8.parse(keyObj.key);
  return CryptoJS.RC4.encrypt(message, key).toString()
}

const rc4_decode = (keyObj, message) => {
  if (keyObj.code != CryptoCode.RC4) {
    return 'Crypto mismatch!'
  }
  var key = CryptoJS.enc.Utf8.parse(keyObj.key);

  const decryptResult = CryptoJS.RC4.decrypt(message, key)
  return decryptResult.toString(CryptoJS.enc.Utf8)
}

const aes_encode = (keyObj, message) => {
  if (keyObj.code != CryptoCode.AES) {
    return 'Crypto mismatch!'
  }
  console.log('aes_encode', keyObj.key, keyObj.iv);

  var key = CryptoJS.enc.Utf8.parse(keyObj.key);
  var iv = CryptoJS.enc.Utf8.parse(keyObj.iv);
  return CryptoJS.AES.encrypt(message, key, { mode: CryptoJS.mode.CBC, iv, padding: CryptoJS.pad.Pkcs7 }).toString()
}

const aes_decode = (keyObj, message) => {
  if (keyObj.code != CryptoCode.AES) {
    return 'Crypto mismatch!'
  }
  var key = CryptoJS.enc.Utf8.parse(keyObj.key);
  var iv = CryptoJS.enc.Utf8.parse(keyObj.iv);
  const decryptResult = CryptoJS.AES.decrypt(message, key, { mode: CryptoJS.mode.CBC, iv, padding: CryptoJS.pad.Pkcs7 })
  return decryptResult.toString(CryptoJS.enc.Utf8)
}
function a() {
  // Encryption Function
  function encryptAES(message, keyo, ivo) {
    // Convert the key and IV to a format supported by CryptoJS
    var key = CryptoJS.enc.Utf8.parse(keyo);
    var iv = CryptoJS.enc.Utf8.parse(ivo);

    // Using AES encryption
    var encrypted = CryptoJS.AES.encrypt(message, key, {
      iv: iv,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
    });

    // Returns the Base64-encoded encrypted result
    return encrypted.toString();
  }

  // For example
  var message = "Hello, World!";
  var key = "1234567890123456"; // 16-byte key
  var iv = "1234567890123456";  // 16 bytes IV

  var encryptedMessage = encryptAES(message, key, iv);
  console.log("Encrypted Message: " + encryptedMessage);
}

const chacha20_encode = (keyObj, message) => {
  var key = keyObj.key;
  var nonce = keyObj.iv;
  if (keyObj.code != CryptoCode.ChaCha20) {
    return 'Crypto mismatch!';
  }

  if (!key || key.length < 32) {
    return 'error: key length must be at least 32 bytes'
  }

  if (nonce && nonce.length > 16) {
    return 'error: nonce length must not exceed 16 bytes'
  }
  const cipher = new ChaCha20Poly1305(new TextEncoder().encode(key))
  const a = cipher.seal(
      new TextEncoder().encode(nonce),
      new TextEncoder().encode(message)
  )
  console.log(a)
  return intArrayToBase64(a)
}
const chacha20_decode = (keyObj, message) => {
  var key = keyObj.key;
  var nonce = keyObj.iv;
  if (keyObj.code != CryptoCode.ChaCha20) {
    return 'Crypto mismatch!'
  }

  if (!key || key.length < 32) {
    return 'error: key length must be at least 32 bytes'
  }

  if (nonce && nonce.length > 16) {
    return 'error: nonce length must not exceed 16 bytes'
  }
  const cipher = new ChaCha20Poly1305(new TextEncoder().encode(key))
  const a = cipher.open(
      new TextEncoder().encode(nonce),
      base64ToUint8Array(message)
  )
  return new TextDecoder().decode(a)
}

const tripledes_encode = (keyObj, message) => {
  var key = CryptoJS.enc.Utf8.parse(keyObj.key);
  var iv = CryptoJS.enc.Utf8.parse(keyObj.iv);
  if (keyObj.code != CryptoCode.TripleDES) {
    return 'Crypto mismatch!'
  }
  return CryptoJS.TripleDES.encrypt(message, key, {
    iv: iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  }).toString()
}

const tripledes_decode = (keyObj, message) => {

  var key = CryptoJS.enc.Utf8.parse(keyObj.key);
  var iv = CryptoJS.enc.Utf8.parse(keyObj.iv);
  if (keyObj.code != CryptoCode.TripleDES) {
    return 'Crypto mismatch!'
  }
  const decryptResult = CryptoJS.TripleDES.decrypt(message, key, {
    iv: iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  })
  return decryptResult.toString(CryptoJS.enc.Utf8)
}

const blowfish_encode = (keyObj, message) => {
  var key = keyObj.key;
  var iv = keyObj.iv;
  if (keyObj.code != CryptoCode.Blowfish) {
    return 'Crypto mismatch!'
  }
  if (iv && iv.length > 8) {
    return 'error: iv length must not exceed 8 bytes'
  }
  key = CryptoJS.enc.Utf8.parse(key)
  iv = CryptoJS.enc.Utf8.parse(iv)
  var ciphertext = CryptoJS.Blowfish.encrypt(message, key, {
    iv: iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  })
  return ciphertext.toString()
}

const blowfish_decode = (keyObj, message) => {
  var key = keyObj.key;
  var iv = keyObj.iv;
  if (keyObj.code != CryptoCode.Blowfish) {
    return 'Crypto mismatch!'
  }
  if (iv && iv.length > 8) {
    return 'error: iv length must not exceed 8 bytes'
  }
  iv = CryptoJS.enc.Utf8.parse(iv)
  key = CryptoJS.enc.Utf8.parse(key)
  var decrypted = CryptoJS.Blowfish.decrypt(message, key, {
    iv: iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  })
  return decrypted.toString(CryptoJS.enc.Utf8)
}

const salsa20_encode = (keyObj, message) => {
  var key = keyObj.key;
  var nonce = keyObj.iv;
  if (keyObj.code != CryptoCode.Salsa20) {
    return 'Crypto mismatch!';
  }

  if (!key || key.length < 32) {
    return 'error: key length must be at least 32 bytes'
  }

  if (nonce && nonce.length > 8) {
    return 'error: nonce length must not exceed 8 bytes'
  }
  var a = streamXOR(
      new TextEncoder().encode(key),
      new TextEncoder().encode(nonce),
      new TextEncoder().encode(message),
      new TextEncoder().encode(message)
  )
  return intArrayToBase64(a)
}
const salsa20_decode = (keyObj, message) => {
  var key = keyObj.key;
  var nonce = keyObj.iv;
  if (keyObj.code != CryptoCode.Salsa20) {
    return 'Crypto mismatch!'
  }

  if (!key || key.length < 32) {
    return 'error: key length must be at least 32 bytes'
  }

  if (nonce && nonce.length > 8) {
    return 'error: nonce length must not exceed 8 bytes'
  }
  const a = streamXOR(
      new TextEncoder().encode(key),
      new TextEncoder().encode(nonce),
      base64ToUint8Array(message),
      base64ToUint8Array(message)
  )
  return new TextDecoder().decode(a)
}

function genRandomKeyObj(len, code, ivLen, seed, seedi) {
  console.log('genRandomKeyObj seed', seed);

  if (seed) {
    return {
      key: seed,
      code: code,
      iv: seedi
    }
  }

  var keyBytes = new Uint8Array(len);
  window.crypto.getRandomValues(keyBytes);
  var iv = undefined
  if (ivLen > 0) {
    iv = new Uint8Array(ivLen);
    window.crypto.getRandomValues(iv);
  }
  return {
    key: intArrayToBase64(keyBytes).slice(0, len),
    code: code,
    iv: intArrayToBase64(iv).slice(0, ivLen)
  }
}


const CryptoCode = {
  RC4: 0,
  AES: 1,
  ChaCha20: 2,
  TripleDES: 3,
  Blowfish: 4,
  Salsa20: 5,
}

const i8 = "12345678";
const i12 = "123456781234";
const i16 = "1234567812345678";

const RC4 = {
  encode: rc4_encode,
  decode: rc4_decode,
  randomKeyObj: (seed) => {
    var seedi = undefined
    if (seed) {
      seed = seed.slice(0, 16)
      // seedi = i8
    }
    return genRandomKeyObj(16, CryptoCode.RC4, 0, seed, seedi)
  }
}
const AES = {
  encode: aes_encode,
  decode: aes_decode,
  randomKeyObj: (seed) => {
    var seedi = undefined
    if (seed) {
      seed = seed.slice(0, 16)
      seedi = i16
    }
    return genRandomKeyObj(16, CryptoCode.AES, 16, seed, seedi)
  }
}
const ChaCha20 = {
  encode: chacha20_encode,
  decode: chacha20_decode,
  randomKeyObj: (seed) => {

    var seedi = undefined
    if (seed) {
      seed = seed.slice(0, 32)
      seedi = i12
    }
    return genRandomKeyObj(32, CryptoCode.ChaCha20, 12, seed, seedi)
  }
}
const TripleDES = {
  encode: tripledes_encode,
  decode: tripledes_decode,
  randomKeyObj: (seed) => {
    var seedi = undefined
    if (seed) {
      seed = seed.slice(0, 24)
      seedi = i8
    }
    return genRandomKeyObj(24, CryptoCode.TripleDES, 8, seed, seedi)
  }
}
const Blowfish = {
  encode: blowfish_encode,
  decode: blowfish_decode,
  randomKeyObj: (seed) => {
    var seedi = undefined
    if (seed) {
      seed = seed.slice(0, 24)
      seedi = i8
    }
    return genRandomKeyObj(24, CryptoCode.Blowfish, 8, seed, seedi)
  }
}
const Salsa20 = {
  encode: salsa20_encode,
  decode: salsa20_decode,
  randomKeyObj: (seed) => {
    var seedi = undefined
    if (seed) {
      seed = seed.slice(0, 32)
      seedi = i8
    }
    return genRandomKeyObj(32, CryptoCode.Salsa20, 8, seed, seedi)
  }
}



class CryptoUtil {
  constructor(code) {
    this.instance = getCrypto(code)
  }
}

function getCrypto(code) {
  switch (Number(code)) {
    case CryptoCode.RC4:
      return RC4;
    case CryptoCode.AES:
      return AES;
    case CryptoCode.ChaCha20:
      return ChaCha20;
    case CryptoCode.TripleDES:
      return TripleDES;
    case CryptoCode.Blowfish:
      return Blowfish;
    case CryptoCode.Salsa20:
      return Salsa20;
  }
}

CryptoUtil.prototype.encode = function (keyObj, message) {
  return this.instance.encode(keyObj, message)
}


CryptoUtil.prototype.decode = function (keyObj, message) {
  return this.instance.decode(keyObj, message)
}


CryptoUtil.prototype.randomKeyObj = function (seed, seedi) {
  return this.instance.randomKeyObj(seed, seedi)
}

CryptoUtil.prototype.encryptText = function (instance, message) {

  var sendCke = instance.sendCke

  var keyObj = {
    key: '1234567812345678',
    code: 1,
    iv: '1234567812345678'
  }
  // keyObj = instance.keyObj
  var encrypted = this.encode(keyObj, message)

  return {
    encrypted,
    sendCke,
    code: instance.code
  }
}

CryptoUtil.prototype.decryptText = function (instance, paramObj) {
  // var instance = context.state.instance
  var keyForKeyObj = instance.keyForKeyObj

  var keyObj;
  // keyObj = this.decode(keyForKeyObj, paramObj.keyEncrypted);
  // keyObj = JSON.parse(keyObj)
  keyObj = {
    key: '1234567812345678',
    code: 1,
    iv: '1234567812345678'
  }
  var content = this.decode(keyObj, paramObj.content)

  console.log('prototype.decryptText', content, paramObj)
  // resolve(content)
  return content
}
CryptoUtil.prototype.toNotEncrypted = function (msgInfo, plainText) {
  msgInfo.encrypted = msgInfo.content
  msgInfo.content = plainText
  msgInfo.codeToShow = msgInfo.code
  delete msgInfo.code
  return msgInfo
}

function convertWordArrayToUint8Array(wordArray) {

  // var arrayOfWords = wordArray.hasOwnProperty("words") ? wordArray.words : [];
  // var length = wordArray.hasOwnProperty("sigBytes") ? wordArray.sigBytes : arrayOfWords.length * 4;

  var arrayOfWords = Object.hasOwn(wordArray, "words") ? wordArray.words : [];
  var length = Object.hasOwn(wordArray, "sigBytes") ? wordArray.sigBytes : arrayOfWords.length * 4;
  var uInt8Array = new Uint8Array(length), index = 0, word, i;

  for (i = 0; i < length; i++) {
    word = arrayOfWords[i];
    uInt8Array[index++] = word >> 24;
    uInt8Array[index++] = (word >> 16) & 0xff;
    uInt8Array[index++] = (word >> 8) & 0xff;
    uInt8Array[index++] = word & 0xff;
  }
  return uInt8Array;
}


CryptoUtil.prototype.encryptFile = function (fileResult, key, file) {
  var wordArray = CryptoJS.lib.WordArray.create(fileResult);

  var encrypted = CryptoJS.AES.encrypt(wordArray, key).toString();

  // 根据实际场景返回
  // return CryptoJS.enc.Base64.stringify(encrypt.ciphertext)

  // var b64 = CryptoJS.enc.Base64.stringify(encrypted.ciphertext)
  var typedArray = convertWordArrayToUint8Array(encrypted);
  console.log('encrypted', encrypted, encrypted.toString(), typedArray);

  // var decrypted = CryptoJS.AES.decrypt(encrypted, key);
  // console.log('encryptFile decrypted', decrypted, key);

  // console.log('reader decrypted', CryptoJS.AES.decrypt(encrypted.toString(), key));

  // console.log('reader decrypted2', CryptoJS.AES.decrypt(CryptoJS.enc.Base64.parse(CryptoJS.enc.Base64.stringify(encrypted.ciphertext)), key));

  var fileEnc = new Blob([encrypted]);
  var resFile = new File([fileEnc], file.name, { type: file.type/*'image/png'*/ });

  // var reader = new FileReader();
  // reader.readAsText(resFile);
  // reader.onload = () => {
  //     console.log('reader result', reader.result.toString());
  //     // console.log('reader', CryptoJS.enc.Base64.parse(reader.result));
  //
  //     var f = CryptoJS.AES.decrypt(reader.result, key);
  //
  //     console.log('reader decrypted', f);
  // }

  return resFile;
}

CryptoUtil.prototype.encryptFileImpl = function (fileResult, key, filemeta) {
  var wordArray = CryptoJS.lib.WordArray.create(fileResult);

  var encrypted = CryptoJS.AES.encrypt(wordArray, key).toString();

  // return CryptoJS.enc.Base64.stringify(encrypt.ciphertext)

  // var b64 = CryptoJS.enc.Base64.stringify(encrypted.ciphertext)
  var typedArray = convertWordArrayToUint8Array(encrypted);
  console.log('encrypted', encrypted, encrypted.toString(), typedArray);

  // var decrypted = CryptoJS.AES.decrypt(encrypted, key);
  // console.log('encryptFile decrypted', decrypted, key);

  // console.log('reader decrypted', CryptoJS.AES.decrypt(encrypted.toString(), key));

  // console.log('reader decrypted2', CryptoJS.AES.decrypt(CryptoJS.enc.Base64.parse(CryptoJS.enc.Base64.stringify(encrypted.ciphertext)), key));

  var fileEnc = new Blob([encrypted]);
  var resFile = new File([fileEnc], filemeta.name, { type: filemeta.type/*'image/png'*/ });

  // var reader = new FileReader();
  // reader.readAsText(resFile);
  // reader.onload = () => {
  //     console.log('reader result', reader.result.toString());
  //     // console.log('reader', CryptoJS.enc.Base64.parse(reader.result));
  //
  //     var f = CryptoJS.AES.decrypt(reader.result, key);
  //
  //     console.log('reader decrypted', f);
  // }

  return resFile;
}

CryptoUtil.prototype.encryptFileReal = function (instance, file) {
  return new Promise((resolve, reject) => {
    var code = 0
    var keyObj = {
      code,
      key: '12345678'
    }
    var reader = new FileReader();
    reader.readAsArrayBuffer(file);
    var filemeta = {
      name: file.name,
      type: file.type,
    }
    reader.onload = () => {
      console.log('reader', reader.result);
      var fileEncrypted = new CryptoUtil(code).encryptFileImpl(reader.result, keyObj.key, filemeta)
      resolve(fileEncrypted)
    }
  });
}

CryptoUtil.prototype.decryptFileReal = function (instance, url) {
  // var url = paramObj.url
  return new Promise((resolve, reject) => {let accessToken = sessionStorage.getItem("accessToken");
    fetch(url, {headers: {accessToken}}).then(res => {
      //
      const header = res.headers.get('content-type');
      console.log('fetch res', res, header)
      // const parts = header.split(';');
      // var filename = parts[1].split('=')[1];
      res.blob().then(file => {
        var code = 0
        var keyObj = {
          code,
          key: '12345678'
        }
        console.log('fetch', file, url.split('/').at(-1));
        // var filemeta = {
        //     name: file.name,
        //     type: file.type,
        // }
        var filemeta = {
          name: url.split('/').at(-1),
          type: res.headers.get('content-type'),
          // type: 'image/png',
        }
        var reader = new FileReader();
        reader.readAsText(file);
        reader.onload = () => {
          console.log('reader.result', reader.result);
          var decryptedFile = new CryptoUtil(code).decryptFile(reader.result, keyObj.key, filemeta);
          console.log('decryptedFile', decryptedFile)
          resolve(decryptedFile)
        }
      }).catch(e=>{
        console.log(e);
        resolve(null)
      })
      // return res.blob()
    })
  });
}

CryptoUtil.prototype.decryptFile = function (fileResult, key, file) {
  var decrypted = CryptoJS.AES.decrypt(fileResult, key);               // Decryption: I: Base64 encoded string (OpenSSL-format) -> O: WordArray
  console.log('decrypted', decrypted, key);
  if (!file) {
    file = {
      name: 'test.png',
      type: 'image/png',
    }
  }

  var typedArray = convertWordArrayToUint8Array(decrypted);               // Convert: WordArray -> typed array

  var fileDec = new Blob([typedArray]);

  return new File([fileDec], file.name, { type: file.type })
}

CryptoUtil.prototype.i16 = '1234567890123456'
CryptoUtil.prototype.i12 = '123456789012'
CryptoUtil.prototype.i8 = '12345678'

CryptoUtil.prototype.testAES = function () {
  function encryptAES(message, keyo, ivo) {
    var key = CryptoJS.enc.Utf8.parse(keyo);
    var iv = CryptoJS.enc.Utf8.parse(ivo);
    console.log('aes_encode', key, iv);
    var encrypted = CryptoJS.AES.encrypt(message, key, {
      iv: iv,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
    });

    // Returns the Base64-encoded encrypted result
    return encrypted.toString();
  }

  // For example
  var message = "Hello, World!";
  var key = "1234567890123456";
  var iv = "1234567890123456";

  var encryptedMessage = encryptAES(message, key, iv);
  console.log("Encrypted Message: " + encryptedMessage);
}

CryptoUtil.prototype.testsal = function () {
  var key = "12345678123456781234567812345678"
  var nonce = "12345678"
  var message = "123467812345678"
  var a = streamXOR(
      new TextEncoder().encode(key),
      new TextEncoder().encode(nonce),
      new TextEncoder().encode(message),
      new TextEncoder().encode(message)
  )
  console.log(a, intArrayToBase64(a))
  a = streamXOR(
      new TextEncoder().encode(key),
      new TextEncoder().encode(nonce),
      a,
      new TextEncoder().encode(message)
  )

  console.log(a, new TextDecoder().decode(a))
}

function toNotEncrypted(msgInfo, plainText) {
  msgInfo.encrypted = msgInfo.content
  msgInfo.content = plainText
  msgInfo.codeToShow = msgInfo.code
  delete msgInfo.code
  return msgInfo
}

export { RC4, AES, ChaCha20, TripleDES, Blowfish, Salsa20, toNotEncrypted, CryptoUtil }